package edu.nus.cs4243.recon.ui.preview;

import java.util.Map;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.MouseWheelListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;

import com.jreiffers.uihandlers.EventHandler;
import com.jreiffers.uihandlers.UiHandler;

import edu.nus.cs4243.recon.model.Feature;
import edu.nus.cs4243.recon.model.HasCoordinates3d;
import edu.nus.cs4243.recon.model.Point3d;
import edu.nus.cs4243.recon.model.linalg.Matrix4;
import edu.nus.cs4243.recon.model.linalg.Vector3;
import edu.nus.cs4243.recon.model.linalg.Vector4;
import edu.nus.cs4243.recon.model.models.Model;
import edu.nus.cs4243.recon.model.project.Project;
import edu.nus.cs4243.recon.ui.ImageControlGlobalState;

public class ModelControl extends Canvas implements MouseListener, MouseMoveListener,
		MouseWheelListener {

	/**
	 * The field of view angle, in degrees, in the y direction.
	 */
	private static final double FOVY = 60;

	private Matrix4 projection = new Matrix4();
	private Matrix4 modelview = new Matrix4();
	private double zNear = 0.1;
	private double zFar = 100;

	private Model currentModel;
	private double yaw, pitch, distance = 10;

	private boolean mouseDown;
	private int previousMouseX, previousMouseY;

	public ModelControl(Composite parent, int style) {
		super(parent, style);
		addMouseListener(this);
		addMouseMoveListener(this);
		addMouseWheelListener(this);
		UiHandler.setup(this);
	}

	private void makePerspective() {
		final Rectangle clientArea = getClientArea();
		double aspect = clientArea.width / (double) clientArea.height;

		projection.loadIdentity();
		projection.perspective(FOVY / 180 * Math.PI, aspect, zNear, zFar);
	}

	private void makeModelView() {
		if (currentModel == null)
			return;

		HasCoordinates3d center = currentModel.getCenter();

		double yawCos = Math.cos(yaw);
		Vector3 eye = new Vector3(yawCos * Math.cos(pitch), yawCos * Math.sin(pitch), Math.sin(yaw));
		eye.scale(distance / eye.length());

		modelview.loadIdentity();
		modelview.lookAt(eye.x + center.getX(), eye.y + center.getY(), eye.z + center.getZ(),
				center.getX(), center.getY(), center.getZ(), 0, 1, 0);
	}

	@EventHandler(event = SWT.Resize)
	public void onResize(Event event) {
		makePerspective();
	}

	@EventHandler
	public void onPaint(PaintEvent event) {
		final Rectangle clientArea = getClientArea();
		event.gc.setBackground(new Color(Display.getCurrent(), 0, 0, 0));
		event.gc.fillRectangle(clientArea);

		if (currentModel == null)
			return;

		double halfWidth = clientArea.width / 2.0;
		double halfHeight = clientArea.height / 2.0;

		event.gc.setForeground(new Color(Display.getCurrent(), 255, 255, 255));

		Vector4 v = new Vector4();
		Vector4 v2 = new Vector4();
		Map<Feature, Point3d> featureMap = currentModel.getPointMap();
		for (Map.Entry<Feature, Point3d> e : featureMap.entrySet()) {
			Point3d p = e.getValue();

			project(v, p);

			if (v.z >= 0) {
				int x = (int) ((v.x + 1) * halfWidth);
				int y = (int) ((v.y + 1) * halfHeight);
				event.gc.drawPoint(x, y);

				final Project project = ImageControlGlobalState.getState().getProject();
				if (project != null) {
					for (Feature f : project.connectedFeatures(e.getKey())) {
						Point3d p2 = featureMap.get(f);
						if (f.getId() < e.getKey().getId() && p2 != null) {
							project(v2, p2);
							int x2 = (int) ((v2.x + 1) * halfWidth);
							int y2 = (int) ((v2.y + 1) * halfHeight);
							event.gc.drawLine(x, y, x2, y2);
						}
					}
				}
			}
		}
	}

	private void project(Vector4 v, Point3d p) {
		v.x = p.x;
		v.y = p.y;
		v.z = p.z;
		v.w = 1;

		modelview.mul(v);
		projection.mul(v);

		v.scale(1 / v.w);
	}

	@Override
	public void mouseDoubleClick(MouseEvent event) {
		final Rectangle clientArea = getClientArea();
		double halfWidth = clientArea.width / 2.0;
		double halfHeight = clientArea.height / 2.0;
		Vector4 v = new Vector4();
		Map<Feature, Point3d> featureMap = currentModel.getPointMap();
		for (Map.Entry<Feature, Point3d> e : featureMap.entrySet()) {
			Point3d p = e.getValue();
			project(v, p);
			System.out.println("  " + e.getValue() + " -> " + (v.x + 1) * halfWidth + ", "
					+ (v.y + 1) * halfHeight + ", " + v.z);
		}
	}

	@Override
	public void mouseDown(MouseEvent e) {
		mouseDown = true;
		previousMouseX = e.x;
		previousMouseY = e.y;
	}

	@Override
	public void mouseUp(MouseEvent e) {
		mouseDown = false;
	}

	@Override
	public void mouseMove(MouseEvent e) {
		if (mouseDown) {
			int dx = e.x - previousMouseX;
			int dy = e.y - previousMouseY;

			pitch += dx / 100.0;
			yaw += dy / 100.0;
			makeModelView();
			redraw();

			previousMouseX = e.x;
			previousMouseY = e.y;
		}
	}

	public void setCurrentModel(Model model) {
		currentModel = model;
		distance = model.getMaximumDistanceFromCenter() * 3;
		System.out.println("Distance: " + distance);
		makeModelView();

		System.out.println(model);

		redraw();
	}

	@Override
	public void mouseScrolled(MouseEvent e) {
		distance *= Math.pow(1.05, e.count);
		makeModelView();
		redraw();
	}

}
